package cn.edu.fzu.delivery.http;

import static java.nio.charset.StandardCharsets.UTF_8;

import java.io.IOException;
import java.nio.charset.Charset;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import org.apache.commons.collections.MapUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import okhttp3.*;

final class OkHttp extends AbstractHttpClient {

    private static final Logger                 log                       = LoggerFactory.getLogger(OkHttp.class);

    private static final RequestBody            EMPTY_REQUEST_BODY        = RequestBody.create(EMPTY_BYTES, null);

    private static final SimpleX509TrustManager SIMPLE_X509_TRUST_MANAGER = new SimpleX509TrustManager();
    private static final TrustManager[]         TRUST_MANAGERS            = { SIMPLE_X509_TRUST_MANAGER };
    private static final SimpleHostnameVerifier SIMPLE_HOSTNAME_VERIFIER  = new SimpleHostnameVerifier();

    private final OkHttpClient                  client;

    OkHttp(long connectTimeout, long readTimeout) {
        this(connectTimeout, readTimeout, UTF_8);
    }


    OkHttp(long connectTimeout, long readTimeout, Charset charset) {

        this.charset = charset;

        this.client = new OkHttpClient.Builder()
                // 不缓存
                .cache(null)
                // 不支持 cookie
                .cookieJar(CookieJar.NO_COOKIES)
                // 连接失败重试
                .retryOnConnectionFailure(true)
                // 连接超时时间
                .connectTimeout(connectTimeout, TimeUnit.MILLISECONDS)
                // 读超时时间
                .readTimeout(readTimeout, TimeUnit.MILLISECONDS)
                // ssl 信任所有证书
                .sslSocketFactory(unsafeSsl(), SIMPLE_X509_TRUST_MANAGER)
                // ssl 不验证域名
                .hostnameVerifier(SIMPLE_HOSTNAME_VERIFIER).build();
    }


    @Override
    protected final byte[] execute(boolean get, String url, Map<String, String> headers, Map<String, String> parameters,
                                   byte[] content)
            throws IOException {

        Request.Builder builder = new Request.Builder();

        if (MapUtils.isNotEmpty(headers)) {
            for (Entry<String, String> entry : headers.entrySet()) {
                builder.header(entry.getKey(), entry.getValue());
            }
        }

        if (get) {
            builder.url(join(parameters, url));
        } else {
            builder.url(url);

            // post body can be byte array or parameter map, byte array first
            if (content != null) {
                builder.post(RequestBody.create(content, null));
            } else if (MapUtils.isNotEmpty(parameters)) {
                builder.post(formBody(parameters));
            } else {
                builder.post(EMPTY_REQUEST_BODY);
            }
        }

        try (Response response = client.newCall(builder.build()).execute()) {
            if (response.isSuccessful()) {
                return response.body().bytes();
            }

            log.error("httpfail: status={}, reason={}, url={}", response.code(), response.message(), url);
            return null;
        }
    }


    public void close() {
        client.dispatcher().executorService().shutdown();
        client.connectionPool().evictAll();
    }


    private RequestBody formBody(Map<String, String> parameters) {
        FormBody.Builder builder = new FormBody.Builder(charset);
        for (Entry<String, String> entry : parameters.entrySet()) {
            builder.add(entry.getKey(), entry.getValue());
        }
        return builder.build();
    }


    /** trust all cert **/
    private SSLSocketFactory unsafeSsl() {
        try {
            SSLContext sslctx = SSLContext.getInstance("TLSv1.2");
            sslctx.init(null, TRUST_MANAGERS, new SecureRandom());
            return sslctx.getSocketFactory();
        } catch (NoSuchAlgorithmException | KeyManagementException e) {
            throw new UnsupportedOperationException(e);
        }
    }

    /** disable hostname verification **/
    static class SimpleHostnameVerifier implements HostnameVerifier {

        @Override
        public boolean verify(final String s, final SSLSession sslSession) {
            return true;
        }

    }

    static class SimpleX509TrustManager implements X509TrustManager {

        private static final X509Certificate[] X509_CERTIFICATES = new X509Certificate[0];

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return X509_CERTIFICATES;
        }


        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            // NO OP
        }


        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            // NO OP
        }
    }

}
